package rm.metrology.shared;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import com.googlecode.objectify.annotation.Indexed;
import com.googlecode.objectify.annotation.Unindexed;

public class MetroEntity extends AbstractEntity {


	private static final long serialVersionUID = 1L;

	@Indexed
	private String fClientRegNo;
	
	@Unindexed
	private String fClientRegName;
	
	@Unindexed
	private String fClientEmail;
	
	@Indexed
	@NotNull(message="[Contact NIC] is REQUIRED")
	@Size(min=7, max=7, message="[Contact NIC] length between 7 and 7")
	private String fContactNIC;
	
	@Unindexed
	@NotNull(message="[Contact Name] is REQUIRED")
	@Size(min=3, max=30, message="[Contact Name] length between 3 and 30")
	private String fContactName;
	
	@Unindexed
	@NotNull(message="[Contact Number] is REQUIRED")
	@Size(min=3, max=30, message="[Contact Number] length between 7 and 7")
	private String fContactNumber;
	
	@Unindexed
	@NotNull(message="[Location Type] is REQUIRED")
	private String fLocationType;
	
	@Unindexed
	@NotNull(message="[Calibration Type] is REQUIRED")
	private String fCalibType;
	
	@Unindexed
	@NotNull(message="[Equipment Type] is REQUIRED")
	private String fEquipType;
	
	@Unindexed
	private String fEquipSerialNo;
	
	@Unindexed
	@NotNull(message="[Equipment Max] is REQUIRED")
	@Size(min=1, max=10000, message="[Equipment Max] between 1 and 10000")
	private Float fEquipMax = 1f;
	
	@Unindexed
	@NotNull(message="[Equipment Type] is REQUIRED")
	@Size(min=1, max=10000, message="[Payment Amount] between 1 and 10000")
	private Integer fPaymentAmt = 0;
	
	@Indexed
	private String fStatus;
	
	@Indexed
	private String fRegNumber;
	
	@Indexed
	private Long fRegExpiry = System.currentTimeMillis();

	@Unindexed
	private List<MeasurementEntity> fMeasurements = new ArrayList<MeasurementEntity>();
	
	
	protected MetroEntity() {	
	}
	
	public MetroEntity(String clientRegNo, String clientRegName, String clientEmail,
					   String contactNIC, String contactName, String contactNumber,
					   LocationEnum locationType,
					   CalibrationEnum calibType, EquipmentEnum equipType,
					   String serialNumber, Float equipMax) {
		fClientRegNo = clientRegNo;
		fClientRegName = clientRegName;
		fClientEmail = clientEmail;
		fContactNIC = contactNIC;
		fContactName = contactName;
		fContactNumber = contactNumber;
		fLocationType = locationType.toString();
		fCalibType = calibType.toString();
		fEquipType = equipType.toString();
		fEquipSerialNo = serialNumber;
		fEquipMax = equipMax;
	}
	
	public final String getClientRegNo() {
		return fClientRegNo;
	}

	public final String getClientRegName() {
		return fClientRegName;
	}

	public final String getClientEmail() {
		return fClientEmail;
	}

	public final String getContactNIC() {
		return fContactNIC;
	}

	public final String getContactName() {
		return fContactName;
	}

	public final String getContactNumber() {
		return fContactNumber;
	}

	public final LocationEnum getLocationType() {
		return LocationEnum.toEnum(fLocationType);
	}

	public final CalibrationEnum getCalibType() {
		return CalibrationEnum.toEnum(fCalibType);
	}

	public final EquipmentEnum getEquipType() {
		return EquipmentEnum.toEnum(fEquipType);
	}

	public final String getEquipSerialNo() {
		return fEquipSerialNo;
	}

	public final Float getEquipMax() {
		return fEquipMax;
	}

	public final Integer getPaymentAmt() {
		return fPaymentAmt;
	}

	public final StatusEnum getStatus() {
		return StatusEnum.toEnum(fStatus);
	}

	public final String getRegNumber() {
		return fRegNumber;
	}

	public final Long getRegExpiry() {
		return fRegExpiry;
	}

	public final List<MeasurementEntity> getMeasurements() {
		return Collections.unmodifiableList(fMeasurements);
	}
	
	public final void doInsert(String staff) {
		fStatus = StatusEnum.NEW.toString();
		log(staff, "created: " + toString());
	}

	public void doUpdate(String staff, MetroEntity m) {
		verifyStatus(StatusEnum.NEW, StatusEnum.CALIBRATING, StatusEnum.CALIBRATED, StatusEnum.PAYING);
		fClientRegNo = m.getClientRegNo();
		fClientRegName = m.getClientRegName();
		fClientEmail = m.getClientEmail();
		fContactNIC = m.getContactNIC();
		fContactName = m.getContactName();
		fContactNumber = m.getContactNumber();
		fCalibType = m.getCalibType().toString();
		fEquipType = m.getEquipType().toString();
		fEquipSerialNo = m.getEquipSerialNo();
		fEquipMax = m.getEquipMax();
		log(staff, "updated: " + toString());
	}
	
	public void doEquipmentFail(String staff, String reason) {
		verifyStatus(StatusEnum.NEW);
		fStatus = StatusEnum.WASTED.toString();
		log(staff, "equipment rejected: " + reason);		
	}
	
	public void doEquipmentPass(String staff) {
		verifyStatus(StatusEnum.NEW);
		fStatus = StatusEnum.CALIBRATING.toString();
		log(staff, "equipment accepted: " + toString());		
	}

	public void doMeasurementAdd(String staff, MeasurementEntity m) {
		verifyStatus(StatusEnum.CALIBRATING, StatusEnum.CALIBRATED);
		fMeasurements.add(m);
		fStatus = StatusEnum.CALIBRATED.toString();
		log(staff, "measurement added : " + m.toString());		
	}
	
	public void doMeasurementRemove(String staff, Long measurementCreateAt) {
		verifyStatus(StatusEnum.CALIBRATING, StatusEnum.CALIBRATED);
		int i = fMeasurements.indexOf(measurementCreateAt);
		MeasurementEntity m = fMeasurements.remove(i);
		if (fMeasurements.size() == 0) fStatus = StatusEnum.CALIBRATING.toString();
		log(staff, "measurement removed : " + m.toString());		
	}
	
	public void doMeasurementsEvaluate(String staff) {
		verifyStatus(StatusEnum.CALIBRATED);
		int cnt = 0;
		for (MeasurementEntity mDB : fMeasurements) {
			cnt = mDB.getInaccuracyCount();
		}
		if (cnt == 0) {
			fStatus = StatusEnum.PAYING.toString();
			log(staff, "equipment accurate");
		} else {
			fStatus = StatusEnum.INACCURATE.toString();
			log(staff, "equipment inaccurate : " + cnt);
		}
	}
	
	public void doPayment(String staff, String receipt) {
		verifyStatus(StatusEnum.PAYING);
		Calendar cal = Calendar.getInstance(); 
		cal.set(Calendar.YEAR, 1 + cal.get(Calendar.YEAR));
		fRegExpiry = cal.getTimeInMillis();
		fRegNumber = ("00000000" + getId()).substring(-9);
		fStatus = StatusEnum.REGISTERED.toString();
		log(staff, "registered : " + receipt);		
	}
	
	private void verifyStatus(StatusEnum... statuses) {
		StatusEnum entityStatus = getStatus();
		for (StatusEnum status : statuses) {
			if (status == entityStatus) return;
		}
		throw new IllegalStateException("Change not allowed @ " + fStatus);
	}
	
}
